import { BadRequestException, ForbiddenException, Inject, Injectable, Logger, OnApplicationBootstrap } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { DataSource, In, Repository, TypeORMError } from "typeorm";
import { PermissionEntity, RoleEntity } from "src/entity";
import { RoleAddDto, RoleQueryDto, RoleUpdateDto } from "src/dto/role";
import { ADMIN_ROLE_NAME, Emitter, ROOT_ROLE_ID, getUserInfoKey } from "src/constant";
import { pagination } from "src/common/pagination.query";
import { EventEmitter2 } from "src/common/event2";
import { CACHE_MANAGER, Cache } from "@nestjs/cache-manager";

@Injectable()
export class RoleService implements OnApplicationBootstrap {
    private readonly logger = new Logger(RoleService.name);

    constructor(
        private readonly dataSource: DataSource,
        @Inject(CACHE_MANAGER) private cacheManager: Cache,
        @InjectRepository(RoleEntity) private readonly roleRepository: Repository<RoleEntity>,
        @InjectRepository(PermissionEntity) private readonly permissionEntity: Repository<PermissionEntity>,
        private readonly eventEmitter: EventEmitter2,
    ) {}

    onApplicationBootstrap() {
        return this.initAdminRole();
    }

    async initAdminRole() {
        await this.dataSource.transaction(async (manager) => {
            let admin = await manager.findOneBy(RoleEntity, {
                id: ROOT_ROLE_ID,
            });

            if (!admin) {
                admin = manager.create(RoleEntity, {
                    id: ROOT_ROLE_ID,
                    name: ADMIN_ROLE_NAME,
                    description: "超级管理员",
                    users: [],
                });
            }

            if (admin.name != ADMIN_ROLE_NAME) {
                throw new TypeORMError(
                    `root role id ${ROOT_ROLE_ID} for name should be ${ADMIN_ROLE_NAME}, current be ${admin.name}`,
                );
            }

            admin.permissions = await manager.find(PermissionEntity);

            await manager.save(admin);
        });
    }

    query(dto: RoleQueryDto) {
        this.logger.log(`requset query roles`);
        const qb = this.roleRepository.createQueryBuilder("role").leftJoinAndSelect("role.permissions", "permissions");
        if (dto.where?.name) {
            qb.where("role.name LIKE :name", {
                name: `%${dto.where.name}%`,
            });
        }
        return pagination(qb, dto);
    }

    findByName(name: string) {
        return this.roleRepository.findOne({
            where: {
                name,
            },
            relations: {
                permissions: true,
            },
        });
    }

    async add(dto: RoleAddDto) {
        this.logger.log(`requset add role: ${dto.name}`);

        const role = await this.roleRepository.save(
            this.roleRepository.create({
                ...dto,
                permissions: dto.permissions ? await this.permissionEntity.findBy({ id: In(dto.permissions) }) : [],
            }),
        );

        this.eventEmitter.emitAsync(Emitter.UPDATE_STATUS, "update.role", role);
        return role;
    }

    async update({ permissions, id, ...data }: RoleUpdateDto) {
        this.logger.log(`requset update role id: ${id}`);

        if (!permissions && !data.description) return;

        if (id == ROOT_ROLE_ID) throw new ForbiddenException(`拒绝修改${ADMIN_ROLE_NAME}角色权限.`);

        const newRole = await this.dataSource.transaction(async (manager) => {
            const query = manager.createQueryBuilder(RoleEntity, "role").where("role.id = :id", { id });

            if (permissions && permissions.length > 0) {
                // todo 当用户数量过大该怎么办
                query.leftJoinAndSelect("role.users", "user").leftJoinAndSelect("role.permissions", "perm");
            }

            let role = await query.getOne();

            if (data.description) {
                role.description = data.description;
            }

            if (
                permissions &&
                new Set([...permissions, ...role.permissions.map((item) => item.id)]).size != role.permissions.length
            ) {
                role.permissions = await this.permissionEntity.find({
                    where: { id: In(permissions) },
                });
            }
            return await manager.save(role);
        });

        if (newRole.users && newRole.users.length > 0) {
            this.cacheManager.store.mdel(...newRole.users.map((item) => getUserInfoKey(item.id)));
            this.eventEmitter.emitAsync(Emitter.UPDATE_STATUS, "update.user", ...newRole.users.map((item) => item.id));
        }

        delete newRole.users;
        this.eventEmitter.emitAsync(Emitter.UPDATE_STATUS, "update.role", ...[newRole]);
    }

    async remove(id: number) {
        this.logger.log(`requset remove role id: ${id}`);

        if (id === ROOT_ROLE_ID) throw new ForbiddenException("拒绝删除超级管理角色");

        const role = await this.roleRepository
            .createQueryBuilder("role")
            // todo 当用户数量过大该怎么办
            .leftJoinAndSelect("role.users", "user")
            .where("role.id = :id", { id })
            .getOne();

        if (!role) throw new ForbiddenException("角色不存在");

        const result = await this.roleRepository.delete(id);
        if (result.affected == 0) throw new BadRequestException("删除失败");

        if (role.users && role.users.length > 0) {
            this.cacheManager.store.mdel(...role.users.map((item) => getUserInfoKey(item.id)));

            this.eventEmitter.emitAsync(Emitter.UPDATE_STATUS, "update.user", ...role.users.map((item) => item.id));
        }
        this.eventEmitter.emitAsync(Emitter.UPDATE_STATUS, "remove.role", id);
    }
}
